/**
 * @file bsp_iflash.c
 * @brief 片内FLASH板载驱动
 * @author  ()
 * @version 1.0
 * @date 2024-09-13
 * 
 * @copyright Copyright (C) 2019 Queclink Wireless Solutions (ShenZhen) Co., Ltd. All Rights Reserved.
 * 
 * @par 修改日志:
 * <table>
 * <tr><th>Date       <th>Version <th>Author  <th>Description
 * <tr><td>2024-09-13 <td>1.0     <td>     <td>内容
 * </table>
 */
/* includes ---------------------------------------------------------------------------------------------------------*/
#include "hal_adapter.h"
#include "bsp_iflash.h"

#include "global.h"
#ifdef STM32U575xx
#include "core_cm33.h"
#endif


/**
 * @brief	bsp flash状态初始化
 *
 */
void bsp_iflash_init(void)
{
}

/**
 * @brief 	bsp flash状态去初始化
 *
 */
void bsp_iflash_deinit(void)
{
}



/**
 * @brief
 *
 * @param addr 	flash待写入数据目的地址
 * @param buf 	被写入数据的源地址
 * @param len 	代写入FLASH的QUARD WORD数量（16Byte），STM32u5xx片内FLASH数据存储的标准大小为16Byte。
 * @return size_t 	成功写入的QUARD WORD数量
 */
size_t bsp_iflash_write_random(unsigned int addr, const void *buf, size_t len)
{
    #define SECTOR_SIZE (8192)

#ifdef STM32U575xx
    uint8_t  cache[SECTOR_SIZE] = {0};
    uint32_t size = 0;
    uint32_t sect_addr = addr; // 待写入地址分区

    while(size < len)
    {
        // uint32_t wr_size = SECTOR_SIZE - (sect_addr % SECTOR_SIZE);
        uint32_t fill_size = 0;
        
        // read out sector data and write back;
        bsp_iflash_read(sect_addr - addr % SECTOR_SIZE, cache, SECTOR_SIZE);

        if(len + sect_addr % SECTOR_SIZE < SECTOR_SIZE ){
            fill_size = sect_addr % SECTOR_SIZE + len;
        }
        else{
            fill_size = SECTOR_SIZE;
        }

        // fill the data to the sector buffer
        for(uint32_t i = sect_addr % SECTOR_SIZE; i < fill_size; i++)
        {
            cache[i] = *((uint8_t *)buf + size);
            size++;
        }

        // erase the sector
        bsp_iflash_erase(sect_addr - sect_addr % SECTOR_SIZE, 1);

        // write back the sector data
        bsp_iflash_write(sect_addr - sect_addr % SECTOR_SIZE, cache, SECTOR_SIZE / 4);
        sect_addr = addr + size;
    }

    return len - size;
#else
    return 0; // need to implement for stm32h563xx
#endif
}


/**
 * @brief
 *
 * @param addr 	flash待写入数据目的地址
 * @param buf 	被写入数据的源地址
 * @param len 	代写入FLASH的QUARD WORD数量（16Byte），STM32u5xx片内FLASH数据存储的标准大小为16Byte。
 * @return size_t 	成功写入的QUARD WORD数量
 */
size_t bsp_iflash_write(unsigned int addr, const void *buf, size_t len)
{
    HAL_StatusTypeDef status;
    size_t size = 0;
    uint8_t index = 4;
    uint32_t *dest_addr = (uint32_t *)addr;
    uint32_t *src_addr = (uint32_t *)buf;

    uint32_t primask_bit;
    __IO uint32_t *reg_cr;

    if (HAL_FLASH_Unlock() != HAL_OK)
    {
        Error_Handler();
    }

    /* Process Locked */
    __HAL_LOCK(&pFlash);

    /* Reset error code */
    pFlash.ErrorCode = HAL_FLASH_ERROR_NONE;

    /* Wait for last operation to be completed */
    status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);

    if (status == HAL_OK)
    {
        pFlash.ProcedureOnGoing = FLASH_TYPEPROGRAM_QUADWORD;

        /* Access to SECCR or NSCR registers depends on operation type */
        reg_cr = IS_FLASH_SECURE_OPERATION() ? &(FLASH->SECCR) : &(FLASH_NS->NSCR);

        /* Set PG bit */
        SET_BIT((*reg_cr), FLASH_NSCR_PG);

        /* Check the parameters */
        assert_param(IS_FLASH_PROGRAM_ADDRESS(addr));

        /* Access to SECCR or NSCR registers depends on operation type */
        reg_cr = IS_FLASH_SECURE_OPERATION() ? &(FLASH->SECCR) : &(FLASH_NS->NSCR);

        /* Set PG bit */
        SET_BIT((*reg_cr), FLASH_NSCR_PG);

        /* Enter critical section: Disable interrupts to avoid any interruption during the loop */
        primask_bit = __get_PRIMASK();
        __disable_irq();

        size = index * len;
        /* Program the quad-word */
        do
        {
            *dest_addr = *src_addr;
            dest_addr++;
            src_addr++;
            size--;
        } while (size != 0U);

        /* Exit critical section: restore previous priority mask */
        __set_PRIMASK(primask_bit);

        /* Wait for last operation to be completed */
        status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);

        /* If the program operation is completed, disable the PG (and BWR Bit in Burst programming mode) */
        CLEAR_BIT((*reg_cr), FLASH_TYPEPROGRAM_QUADWORD);
    }

    /* Process Unlocked */
    __HAL_UNLOCK(&pFlash);

    if (HAL_FLASH_Lock() != HAL_OK)
    {
        Error_Handler();
    }

    return (index * len - size) / index;
}

/**
 * @brief
 *
 * @param addr	flash待读取数据目的地址
 * @param buf 	数据存放缓存
 * @param len 	待读取字节数
 * @return size_t	读取的字节数
 */
size_t bsp_iflash_read(unsigned int addr, void *buf, size_t len)
{
    unsigned int offset = 0;

    assert_param(IS_FLASH_PROGRAM_ADDRESS(addr));

    while (len >= 4)
    {
        *((unsigned int *)((unsigned int)buf + offset)) = (unsigned int)(*((volatile unsigned int *)addr));
        offset += 4;
        addr += 4;
        len -= 4;
    }
    if (len >= 2)
    {
        *((unsigned short *)((unsigned int)buf + offset)) = (unsigned short)(*((volatile unsigned short *)addr));
        offset += 2;
        addr += 2;
        len -= 2;
    }
    if (len)
    {
        *((unsigned char *)buf + offset) = (unsigned char)(*((volatile unsigned char *)addr));
        offset += 1;
        // address += 1;
        // len -= 1;
    }
    return offset;
}


/**
 * @brief
 *
 * @param addr	flash待擦除数据目的地址
 * @param len	待擦除页的数量（一页为8KB大小）
 * @return size_t	成功擦除的页数
 */
size_t bsp_iflash_erase(unsigned int addr, size_t len)
{
#ifdef STM32U575xx
    HAL_StatusTypeDef status;

    uint32_t page_index = 0;
    uint32_t bank_index = 0;
    size_t size = len;
    int32_t offset = 0;
    __IO uint32_t *reg_cr;

    offset = addr - BANK_1_ADDR;

    /* unlock flash */
    if (HAL_FLASH_Unlock() != HAL_OK)
    {
        Error_Handler();
    }

    if (offset < 0)
        return -HAL_ERROR;

    bank_index = offset / FLASH_BANK_SIZE + 1;
    page_index = offset / FLASH_PAGE_SIZE;

    /* Check the parameters */
    assert_param(IS_FLASH_PAGE(Page));
    assert_param(IS_FLASH_BANK_EXCLUSIVE(Banks));

    /* Process Locked */
    __HAL_LOCK(&pFlash);

    /* Reset error code */
    pFlash.ErrorCode = HAL_FLASH_ERROR_NONE;

    /* Wait for last operation to be completed */
    status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);

    if (status == HAL_OK)
    {
        pFlash.ProcedureOnGoing = FLASH_TYPEERASE_PAGES;

        /* Access to SECCR or NSCR depends on operation type */
        reg_cr = IS_FLASH_SECURE_OPERATION() ? &(FLASH->SECCR) : &(FLASH->NSCR);

        for (int i = 0; i < len; i++)
        {
            page_index = page_index + i;

            /* Check the parameters */
            if (page_index >= FLASH_PAGE_NB && bank_index == FLASH_BANK_1)
            {
                bank_index = 2;
                page_index -= FLASH_PAGE_NB;
            }
            assert_param(IS_FLASH_PAGE(page_index));

            /* Access to SECCR or NSCR registers depends on operation type */
            reg_cr = IS_FLASH_SECURE_OPERATION() ? &(FLASH->SECCR) : &(FLASH_NS->NSCR);

            if ((bank_index & FLASH_BANK_1) != 0U)
            {
                CLEAR_BIT((*reg_cr), FLASH_NSCR_BKER);
            }
            else
            {
                SET_BIT((*reg_cr), FLASH_NSCR_BKER);
            }

            /* Proceed to erase the page */
            MODIFY_REG((*reg_cr), (FLASH_NSCR_PNB | FLASH_NSCR_PER | FLASH_NSCR_STRT),
                       ((page_index << FLASH_NSCR_PNB_Pos) | FLASH_NSCR_PER | FLASH_NSCR_STRT));

            /* Wait for last operation to be completed */
            status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);

            if (status != HAL_OK)
            {
                /* In case of error, stop erase procedure and return the the number of cleaned pages. */
                size = i;
                break;
            }
        }

        /* If the erase operation is completed, disable the associated bits */
        CLEAR_BIT((*reg_cr), FLASH_TYPEERASE_PAGES);
    }

    /* Process Unlocked */
    __HAL_UNLOCK(&pFlash);

    /* lock flash */
    if (HAL_FLASH_Lock() != HAL_OK)
    {
        Error_Handler();
    }

    return size;
#else
    return 0; // need to implement for stm32h563xx
#endif
}




#if 0 // testcase for internal flash
/***
 *  iflash testcase 
 */

int32_t shell_iflash_write(uint32_t addr, char *wr_data)
{
    printf("write flash: (0x%08X) %s\r\n", addr, wr_data);
    bsp_iflash_write_random(addr, wr_data, strlen(wr_data));

    return 0;
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC) | SHELL_CMD_DISABLE_RETURN,
                 iflash_write, shell_iflash_write, call iflash_write {addr} {string} for internal flash write testcase);

int32_t shell_iflash_read(uint32_t addr, uint16_t len)
{   
    char *buf = qlmalloc(len + 1);

    if(buf){
        bsp_iflash_read(addr, buf, len);
        printf("read flash: (0x%08X) %s\r\n", addr, buf);
        qlfree(buf);
    }

    return 0;
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC) | SHELL_CMD_DISABLE_RETURN,
                 iflash_read, shell_iflash_read, call iflash_read {addr} {len} for internal flash read testcase);
#endif